/**
* Copyrights 2011 Michal Saman, m.saman@designplus.cz.
* This source code is release under the Apache License 2.0.
* http://www.apache.org/licenses/LICENSE-2.0
*
* @file	 process.c
* @brief Process handling and management
*/

#include <env.h>
#include <errno.h>
#include <proc/process.h>
#include <mem/kheap.h>
#include <mem/mem.h>
#include <mem/vmu.h>
#include <string.h>
#include <hal/hal.h>
#include <hal/pit.h>
#include <cpu/cpu.h>
#include <lib/queue.h>

/** Queutes of process ready to run separated by process priority */
queue_t *sh_rdy_q[PROC_SHED_QUEUES];

/** Queutes of wainting processes */
queue_t *sh_wait_q;

/** Queutes of terminated processes */
queue_t *sh_term_q;

/** Current running process */
process_t* current_process = NULL;

/** IDLE process */
process_t* idle_process = NULL;

/** Previous runned process */
process_t* previous_process = NULL;

atomic_t   current_pid;

bool		proc_enabled = false;

/* Temporary registers, used for temporary storage in context switching. */
static uint32_t	tmp_eax;
static uint32_t	tmp_pc;

/**
 * Inicialize multitasking sub-system.
 */
void proc_init() {
	atomic_set(&current_pid, 0);

	current_process = init_process("kernel main", KERNEL_PRIVILEGE, 0, NULL);
	current_process->pcontext.page_dir = _kpage_dir;
	/*
	save_all_regs();
	pcontext_save_from_stack(current_process);
	//kprintf("multitask init PC: 0x%x\n", current_process->pcontext.pc);
	restore_all_regs();
	pcontex_save_pc(current_process);
	*/
	if (proc_enabled)
		return;
	queue_insert((type_t*)current_process, &sh_rdy_q[current_process->priority]);

	idle_process = init_process("IDLE", KERNEL_PRIVILEGE, 9, NULL);
	//queue_insert((type_t*)p, &sh_rdy_q[p->priority]);

	/**
	process_t* p = init_process("kernel main 2", KERNEL_PRIVILEGE, 0);
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);

	p = init_process("kernel test 1", KERNEL_PRIVILEGE, 1);
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);

	p = init_process("kernel test 2", KERNEL_PRIVILEGE, 2);
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);

	p = init_process("kernel test 3", KERNEL_PRIVILEGE, 2);
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);

	p = init_process("use proc", USER_PRIVILEGE, 9);
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);
	*/

	proc_enabled = true;
	pit_reload_handler(mt_pit_irq_handler);
}

/*
 * The fork() function creates a new process.
 * The new process (the child process) is an exact copy of the calling process (the parent process).
 * @return int	If successful, fork() returns 0 in the child process,
 * and returns the process ID of the child process to the parent process. On failure, it returns -1
 */
int proc_fork() {
	disable_interrupts();
	process_t* parent = current_process;
	process_t* p = init_process(current_process->pname, current_process->privilege, current_process->priority, current_process);
	if (!p) return ENOENT;
	if (clone_memspace(current_process, p) != EOK) return ENOENT;			// copy memory space
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);						// save child process to scheduler RDY queue

	//save_all_regs();
	//pcontext_save_from_stack(p);
	//restore_all_regs();
	//pcontex_save_pc(p);
	uint32_t pc = get_pc();				// read PC, it will be entry point for child process!!
	if (parent == current_process) {
		uint32_t esp; __asm__ volatile("mov  %%esp, %0" : "=r"(esp));		// preserve status of stack pointer
		uint32_t ebp; __asm__ volatile("mov  %%ebp, %0" : "=r"(ebp));		// preserve status of ebp register
		p->pcontext.esp = esp;
		p->pcontext.ebp = ebp;
		p->pcontext.pc = pc;
		enable_interrupts();
		return (int)p->pid;			// we are parent process, return child ID
	} else {
		return EOK;					// we are child process, return 0
	}
}

void proc_idle_task() {
	while(true) {
		enable_interrupts();
		cpu_halt();
	}
}

/**
 * Inicialize new PID for process.
 */
pid_t new_pid() {
	atomic_inc(&current_pid);
	return ((pid_t)atomic_read(&current_pid));
}

/**
 * Return PID of current process.
 */
pid_t get_pid() {
	return current_process->pid;
}

/**
 * Kill task by pid.
 * Process is moved to terminated task queue.
 * Routine don't free process resources.
 *
 * @param pid 	Process to kill.
 */
bool proc_kill(pid_t pid) {
	int i, x, c;
	process_t* p;
	bool ret = false;

	// search RDY queue
	for (i=0; i < PROC_SHED_QUEUES; i++) {
		c = queue_count(sh_rdy_q[i]);
		for (x=0; x < c; x++) {
			p = (process_t*)queue_pop(&sh_rdy_q[i]);
			if (p->pid == pid) {
				queue_delete((type_t*)p,&sh_rdy_q[i]);
				p->state == PROC_TERMINATED;
				queue_insert((type_t*)p,&sh_term_q);
				ret = true;
			} else {										// check if proc to kill is parent of some process and clear it
				if (p->parent != NULL)
					if ( (p->parent)->pid == pid )
						p->parent = NULL;
			}
		}
	}

	// search WAIT queue
	c = queue_count(sh_wait_q);
	for (x=0; x < c; x++) {
		p = (process_t*)queue_pop(&sh_wait_q);
		if (p->pid == pid) {
			queue_delete((type_t*)p,&sh_wait_q);
			p->state == PROC_TERMINATED;
			queue_insert((type_t*)p,&sh_term_q);
			ret = true;
		} else {										// check if proc to kill is parent of some process and clear it
			if (p->parent != NULL)
				if ( (p->parent)->pid == pid )
					p->parent = NULL;
		}
	}

	return ret;
}

/**
 * Sleep process.
 * @param p Process to sleep
 */
void proc_sleep(process_t* p) {
	// critical region enter
	queue_delete((type_t*)p, &sh_rdy_q[p->priority]);
	p->state = PROC_WAIT;
	queue_insert((type_t*)p, &sh_wait_q);
	// critical region leave
}

/**
 * Wakeups sleeping process.
 * @param p Process to wakeup
 */
void proc_wakeup(process_t* p) {
	// critical region enter
	queue_delete((type_t*)p, &sh_wait_q);
	p->state = PROC_READY;
	queue_insert((type_t*)p, &sh_rdy_q[p->priority]);
	// critical region leave
}

/**
 * Priority round-robin scheduler.
 * This routine is called for selecting new process to switch.
 */
static __inline__ void proc_scheduler() {
	previous_process = current_process;
	uint8_t pri;
	uint32_t c, x;

	// select next process tu run (first from highest queue priority)
	for (pri=0; pri<PROC_SHED_QUEUES; pri++) {
		c = queue_count(sh_rdy_q[pri]);
		for (x=0; x<c; x++) {
			current_process = queue_pop(&sh_rdy_q[pri]);
			if (current_process) break;
		}
		if (current_process) break;
	}

	// nothing to do, set idle process
	if (current_process == NULL)
		current_process = idle_process;
}

/**
 * This routine is called in periodic interval by PIT IRQ and provides
 * switching of running process.
 */
void proc_dispatcher() {
	if (proc_enabled) {
		proc_scheduler();

		uint8_t pri;
		uint32_t c, x;

		// update process statistic
		uint32_t ticks = pit_get_tick_count();
		uint32_t c_ticks = ticks % PROC_STATS_TICKS;
		if (c_ticks == 0 && ticks!=0) c_ticks = PROC_STATS_TICKS;

		// long term statistic
		previous_process->in_run = previous_process->in_run + (ticks - previous_process->entr_run);		// total time process in rdy queue
		previous_process->entr_rdy_queue = ticks;														// reset tick when process was ready in queue
		current_process->entr_run = ticks;					// reset tick when process was started to run
		current_process->in_rdy_queue = current_process->in_rdy_queue + (ticks - current_process->entr_rdy_queue);		// total time process in rdy queue

		previous_process->c_in_run = previous_process->c_in_run + (c_ticks - previous_process->c_entr_run);
		current_process->c_entr_run = c_ticks;

		// continous statistic (valid for PROC_STATS_TICKS interval)
		if (c_ticks == PROC_STATS_TICKS) {
			// reset continous statistic for all process
			for (pri=0; pri<PROC_SHED_QUEUES; pri++) {
				c = queue_count(sh_rdy_q[pri]);
				queue_t* q = sh_rdy_q[pri];
				for (x=0; x<c; x++) {
					process_t *p = queue_pop(&q);
					p->c_entr_run = 0;
					p->c_in_run_last = p->c_in_run;
					p->c_in_run = 0;
				}
			}
		}

		if (previous_process != current_process) {
			/*
			kprintf("--------\nRunning process: %u\n", previous_process->pid);
			kprintf("Switching to process: %u\n", current_process->pid);
			kprintf("Task switch\nPDBR from 0x%x to 0x%x\n", previous_process->pcontext.page_dir->pbdr, current_process->pcontext.page_dir->pbdr);
			kprintf("PC now at 0x%x, switch to 0x%x\n", previous_process->pcontext.pc, current_process->pcontext.pc);
			kprintf("SP now at 0x%x to 0x%x\n", previous_process->pcontext.esp, current_process->pcontext.esp);
			kprintf("BP now at 0x%x to 0x%x\n", previous_process->pcontext.ebp, current_process->pcontext.ebp);
			*/

			end_hw_interrupt(0);														// re-enable PIC
			pcontext_restore(current_process);											// restore current_process
			switch_memspace(current_process);											// switch memory space
			__asm__ volatile ("sti");
			jump_to_pc(current_process);												// GO GO GO
			// NOT_REACHABLE
		}

	}
}

/**
 * This function is called directly by INT instruction.
 * It's workaround for GCC compiler, because automatic stack framing is difficult to manage
 * and to return stack to original state. This empty function do only saves of all registers, stops interrupts and
 * call to real handler (whitch can use stack framing). Than restore registers and start intterupts again.
 */
void mt_pit_irq_handler() {
	save_all_regs();										// store all regs to stack
	__asm__ volatile ("cli");

	pcontex_save_pc_from_stack(current_process);			// read PC from stack
	pcontext_save_from_stack(current_process);				// read current status of process

	//uint32_t g = pit_ticks % 1000;
	proc_dispatcher();
	pit_ticks++;
	end_hw_interrupt(0);

	restore_all_regs();										// restore registers from stack
	__asm__ volatile ("sti");								// enable interrupts
	__asm__ volatile ("iretl");								// return from interrupt handler
	// NOT_REACHABLE
}

/**
 * Init empty process
 */
process_t*	init_process(char* pname, int privilege, uint8_t priority, process_t* parent) {
	process_t *p = (process_t *)kmalloc(sizeof(process_t));
	p->privilege = privilege;
	p->pid = new_pid();
	p->priority = priority;
	p->parent = parent;
	p->c_entr_run = 0;
	p->c_in_run = 0;
	p->c_in_run_last = 0;
	strcpy(p->pname, pname);

	return p;
}

/** HELPER ROUTINES **/

void proc_info() {

	disable_interrupts();

	int i, x, c;

	// RDY queuetes
	kprintf("Multitasking subsystem debug information.\n");
	kprintf("Process table of processes ready to run:\n");
	for (i=0; i < PROC_SHED_QUEUES; i++) {
		kprintf("Queue for priority ~%u~: ", i);
		c = queue_count(sh_rdy_q[i]);
		kprintf("(count %u):\n", c);
		for (x=0; x < c; x++) {
			proc_info_entry( (process_t*)queue_pop(&sh_rdy_q[i]) );
		}
	}

	// WAINT queue
	kprintf("Process table of waiting processes: ");
	c = queue_count(sh_wait_q);
	kprintf("(count %u):\n", c);
	for (x=0; x < c; x++) {
		proc_info_entry( (process_t*)queue_pop(&sh_wait_q) );
	}

	// TERMINATED queue
	kprintf("Process table of terminated processes: ");
	c = queue_count(sh_term_q);
	kprintf("(count %u):\n", c);
	for (x=0; x < c; x++) {
		proc_info_entry( (process_t*)queue_pop(&sh_term_q) );
	}

	kprintf("Current processes: \n");
	proc_info_entry(current_process);

	enable_interrupts();

}

static void proc_info_entry(process_t* p) {
	uint32_t ticks = pit_get_tick_count();

	kprintf("  >> PID: %u, ", p->pid);
	kprintf("name: %s, ", p->pname);

	uint8_t run_p = (p->in_run * 100 / ticks);
	uint8_t c_run_p = (p->c_in_run_last * 100 / PROC_STATS_TICKS);

	kprintf("cpu usage: %u, ", c_run_p);
	kprintf("cpu usage (long-term): %u\n", run_p);
}
